#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/sendfile.h>
#include <sys/wait.h>
#define MAX (1024*10)
typedef struct HttpRequest
{
    char firstline[MAX];
    char *method;
    char *url;
    char *path;
    char *urlPath;
    char *query_string;
    char *body;
    int   contentLen;
}HttpRequest;
void InitRequest(HttpRequest * p)
{
  p->method = p->url=p->body=p->query_string=p->urlPath=p->path = NULL;
}
int ReadLine(int newfd,char *fristLine)
{
  //把\r\n,\r  转化成\n
  int index = 0;
  char c = 0;
  while(1){
    int size = recv(newfd,&c,1,0);
    if(size<=0){    //没读到或者读到EOF都结束
      return -1;
    }
    if(c == '\r'){
      recv(newfd,&c,1,MSG_PEEK);
      if(c=='\n'){
        recv(newfd,&c,1,0);
      }
      else{
        c = '\n';
      }
    }
    fristLine[index++] = c;
    if(c == '\n'){
        break;
    } 
  }
  return index;
}
int Split(char *line[],char* firstline)
{
	int i=0;
  char * pch=firstline;
  char * pSave = NULL;
  line[i]= strtok_r(pch," ",&pSave);
	while(line[i]!=NULL)
	{
		  line[++i]= strtok_r(NULL," ",&pSave);
	}
  return i;
}
int Parse_Firtst(HttpRequest *t)
{
 	   char *line[MAX]={NULL};
     int retLen = Split(line,t->firstline);
     if(retLen !=3){
       perror("解析首行失败\n");
       return -1;
     }
     t->method = line[0];
     t->url = line[1];
     return 1;
}
int  Parse_Url(char *url,char **urlPath,char **query_string)
{
    if(urlPath == NULL)
      return -1;
    *urlPath = url;
    printf("开始解析url:%s\n",url);
    while(*url!='\0'){
      if(*url =='?')
      {
         *url = '\0';      
         *query_string = ++url;
      }
      else
        url++;
    }
    return 1;
}
int Parse_Header(int newfd,int *contentLen)
{
  while(1)
  {
    char line[MAX]= {0};
    memset(line,0,sizeof(line));
    int len = ReadLine(newfd,line);
    printf("%s\n",line);
    if(len<0){
      return -1;
    }
   if(strcmp(line,"\n")== 0){       //解析到空行结束
      return *contentLen;
    }
    const char *content = "Content-Length ";
    if(strncmp(line,content,strlen(content)) == 0){
      *contentLen = atoi(line+strlen(content));
    }
  }
  return *contentLen;
}
void Handle_404(int newfd)
{
  printf("开始发送\n");
  const char * first_line = "HTTP/1.1 404 Not Found\n";
  const char * black_line = "\n";
  const char * body = "<head><meta http-equiv=\"Content-Type\""
    "content=\"text/html;charset=utf-8\"></head>"
    "<body><h1>您的网页被喵星人吃了!</h1></body>";
  char header[MAX] = {0};
  sprintf(header,"Content-Length: %lu\n",strlen(body));   //一定要记得content-length后面要加换行
  send(newfd,first_line,strlen(first_line),0);
  send(newfd,header,strlen(header),0);
  send(newfd,black_line,strlen(black_line),0);
  send(newfd,body,strlen(body),0);
  return;
}
void GetFilePath(const char * urlpath,char * filepath)
{
  fprintf(stderr,"转换目录为:%s\n",filepath);
  //如果http服务器的根目录进行拼接
  sprintf(filepath,"wwwroot1%s",urlpath);
  fprintf(stderr,"转换目录为:%s\n",filepath);
  //如果urlpath是一个目录,如/image,实际尝试访问/image/index.html
  //因此需要判断这个urlpath是目录还是文件
  struct stat st;
  int ret = stat(filepath,&st);
  if(ret<0){
    printf("查看文件属性失败\n");
    return;
  }
  if(S_ISDIR(st.st_mode)){
    if(filepath[strlen(urlpath)-1] == '/'){
      strcat(filepath,"index.html");
    } 
    else{
      strcat(filepath,"/index.html");
    }
  }
  return ;
}
int GetFileSize(const char * filepath)
{
  struct stat st;
  int ret =stat(filepath,&st);
  if(ret<0){
    return 0;
  }
  return st.st_size;
}
int SendStaticFile(int newfd,const char * filepath)
{
  int fd = open(filepath,O_RDONLY);
  if(fd<0){
    perror("打开文件失败\n");
    return -1;
  }
  int size = GetFileSize(filepath);
  const char *frist_line="HTTP/1.1 200 OK\n";
  const char *block_line="\n";
  char header [MAX];
  sprintf(header,"Content-Length: %d\n",size);
  send(newfd,frist_line,strlen(frist_line),0);
  send(newfd,header,strlen(header),0);
  send(newfd,block_line,strlen(block_line),0);
  //将文件内容作为body发送到socker中
  sendfile(newfd,fd,NULL,size);
  //关闭文件
  close(fd);
  return 200;
}
int handle_staticfile(const HttpRequest* req,int newfd)
{
  //1.拼接目录
  //  当前文件在文件系统的真实路径
  char file_path[MAX]={0};
  GetFilePath(req->urlPath,file_path);
  printf("转化成功的file_path:%s",file_path);
  //2.打开文件,读取文件内容,根据文件内容构造HTTP响应
  //  其中文件内容作为HTTP响应的body部分
  SendStaticFile(newfd,file_path);
  return 0;
}
int Cgi_father(int newfd,HttpRequest * req,int father_read,int father_write)
{
  fprintf(stderr,"开始处理fatherCGI\n");
  //1.查看是否为POST方法,如果是,将它的body部分写入管道,以供子进程的cgi程序获取
  int i =0;
  char c;
  if(strcmp(req->method,"POST") == 0)
  {
      for(;i<req->contentLen;i++)
      {
        recv(newfd,&c,1,0);
        write(father_write,&c,1);
      }
  }
  fprintf(stderr,"开始发送响应\n");
  //2.构建响应格式,再获取子进程cgi返回的body部分发送给newsock
  const char* first_line = "HTTP/1.1 200 OK\n";
  const char* black_line = "\n";
  char body[MAX];
  while(read(father_read,&c,1)>0){
    body[i++] =c;
  }
  fprintf(stderr,"body:%s",body);
  char header[MAX];  
  sprintf(header,"Content-Length :%d\n",i);
  strcat(header,"Content-Type: text/html; charset=UTF-8\n");
  send(newfd,first_line,strlen(first_line),0);
  send(newfd,header,strlen(header),0);
  send(newfd,black_line,strlen(black_line),0);
  send(newfd,body,strlen(body),0);
  return 200;
}
int Cgi_child(HttpRequest * req,int child_read,int child_write)
{
  //1.设置环境变量
  char method[MAX] ={0};
  char query_string[MAX] = {0};
  sprintf(method,"ENV_METHOD=%s",req->method);
  putenv(method);
  if(strcmp(req->method,"GET")== 0){
    sprintf(query_string,"ENV_QUERYSTRING=%s",req->query_string);
    putenv(query_string);
  }else{                      //方法为POST,应该在body部分获取参数,只需要把body长度设置过去就行
    char env_contentLen[MAX] = {0};
    sprintf(env_contentLen,"ENV_CONTENTLEN=%d",req->contentLen);
    putenv(env_contentLen);
  }
  dup2(child_read,0);             //将子进程读取管道重定向到标准输入
  dup2(child_write,1);            //将子进程写入管道重定向到标准输出
  //2.根据传入参数得到:要替换的进程路径
  char filepath[MAX];
  GetFilePath(req->urlPath,filepath);
  fprintf(stderr,"转换目录为:%s\n",filepath);
  if(execl(filepath,filepath,NULL)==-1)
  {
    fprintf(stderr,"进程替换失败\n");
    return 404;
  }
  exit(0);
  return 0;
}
int HandlerCGI(int newfd,HttpRequest *req)
{
  //1.先创建一对匿名管道,因为管道是单向通信的
  int fd1[2];
  int fd2[2];
  pipe(fd1);
  pipe(fd2);
  int father_read = fd1[0];
  int child_write = fd1[1];
  int father_write = fd2[1];
  int child_read = fd2[0];
  int err_code = 404;
  //2.创建子进程
  int ret  =fork();
  if(ret > 0){
    int stat;
    close(child_read);
    close(child_write);
    err_code = Cgi_father(newfd,req,father_read,father_write);
    waitpid(-1,&stat,0);
  }
  else if(ret==0)     //子进程
  {
    close(father_read);
    close(father_write);
    err_code = Cgi_child(req,child_read,child_write);
  }
  else{
    perror("fork erro");
  }
  return err_code;
}
void * Handle_accept(void *fd)
{
   printf("start handle\n");
   int64_t newfd = (int64_t)fd;
   int err_code= 200;
   HttpRequest request;
   InitRequest(&request);
   memset(&request,0,sizeof(request));
   if(ReadLine(newfd,request.firstline)<0)
   {
      err_code =404;
      goto END;
   }
   printf("%s\n",request.firstline);
   if(Parse_Firtst(&request)<0){
     err_code = 404;
     goto END;
   }
   printf("解析首行完成\n%s\t%s\n",request.method,request.url);
   if(Parse_Url(request.url,&request.urlPath,&request.query_string)<0){
     printf("解析url失败\n");
     err_code = 404;
     goto END;
   }
   printf("解析url完成\n%s\t%s\n",request.urlPath,request.query_string);
   if(Parse_Header(newfd,&request.contentLen)<0){
     printf("解析Header失败\n");
     err_code = 404;
     goto END;
   }
   printf("解析Header成功,正文长度: %d\n",request.contentLen);
   if(strcmp(request.method,"GET") == 0&&request.query_string == NULL)
     //GET请求但是却没有query_string 因此为静态页面
   {
      printf("GET方法,且没有query_string\n");
      handle_staticfile(&request,newfd);
      err_code = 404;
      goto END;
   }
   if(strcmp(request.method,"GET") == 0&&request.query_string != NULL)
     //GET请求但是有query_string 因此为动态页面
   {
     printf("ET方法,query_string存在,开始处理cgi(query_string:%s)\n",request.query_string);
     err_code = HandlerCGI(newfd,&request);
   }
   if(strcmp(request.method,"POST") == 0&&request.query_string == NULL)
     //POST请求但是却没有query_string 因此为搜索信息在body里面的动态页面
   {
     printf("POST方法,query_string不存在\n");
     err_code = HandlerCGI(newfd,&request);
   }
   if(strcmp(request.method,"POST") == 0&&request.query_string != NULL)
     //POST请求但是却没有query_string 因此为搜索信息在body里面的动态页面
   {
     printf("POST方法,query_string存在,开始处理cgi(query_string:%s)\n",request.query_string);
     err_code = HandlerCGI(newfd,&request);
   }
END:
   if(err_code ==404){
      printf("开始发送404页面\n");
     Handle_404(newfd);
   }
   close(newfd);
   return NULL;
}
void CreatSock(int *lisfd,const char * port,const char* ip)
{
    //忽略信号
    signal(SIGCHLD,SIG_IGN);
    *lisfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    if(*lisfd<0){ //文件描述符不够的时候创建失败
      perror("creat socket erro");    
      return;
    } 
    printf("%s%s\n",port,ip);
    int p = atoi(port);
    printf("端口号%d\n",p);
    struct sockaddr_in addr;
    addr.sin_family  = AF_INET;
    addr.sin_port =  htons(atoi(port));
    addr.sin_addr.s_addr = inet_addr(ip);
    int ret =bind(*lisfd,(struct sockaddr*)&addr,sizeof(addr));
    if(ret<0){
      perror("bind erro");
      return;
    }
    //开始监听
    listen(*lisfd,5);
    printf("服务器开启成功\n");
    while(1){
        struct sockaddr_in cliaddr;
        socklen_t len = sizeof(cliaddr);
        //接收连接
        int64_t newfd =accept(*lisfd,NULL,NULL);
        if(newfd<0){
          printf("accept new client erro\n");
          continue;
        }
        pthread_t tid;
        pthread_create(&tid,NULL,Handle_accept,(void*)newfd);
        pthread_detach(tid);
    }
}
int main(int argc,char* argv[])
{
  if(argc!=3){
    perror("usage erro");
    return 1;
  }
  int  lisfd;
  printf("%s%s\n",argv[2],argv[1]);
  CreatSock(&lisfd,argv[2],argv[1]);
  return 0;
}
